package com.xy.ddd.core.convertor;

import com.alibaba.fastjson.JSONObject;
import com.xy.ddd.core.annotations.DomainField;
import com.xy.ddd.core.config.DomainRegistryFactory;
import com.xy.ddd.core.constant.LogConstant;
import com.xy.ddd.core.model.DomainModel;
import com.xy.ddd.core.po.BasePo;
import com.xy.ddd.core.service.IDomainService;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.util.Assert;

import java.beans.IntrospectionException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;

public class DefaultConvertor<D extends DomainModel, P extends BasePo> extends AbstractConvertor<D, P> {

    private final List<String> ignoreField = Arrays.asList("serialVersionUID");

    @Override
    public D convert(P po) {
        Class<D> domainModelClass = getDomainModelClass();
        if (domainModelClass == null || po == null) {
            return null;
        }
        D domainModel = null;
        try {
            domainModel = domainModelClass.newInstance();
        } catch (Exception e) {
            LogConstant.frameworkLogger.error("DomainModel实例化失败, PO = {}", JSONObject.toJSON(po), e);
            return null;
        }

        if (domainModel == null) {
            LogConstant.frameworkLogger.warn("DomainModel实例化失败, PO = {}", JSONObject.toJSON(po));
            return null;
        }

        Field[] domainFields = FieldUtils.getAllFields(domainModel.getClass());

        try {
            for (Field field : domainFields) {
                writeProperty(field, po, domainModel);
            }
        } catch (Exception e) {
            LogConstant.frameworkLogger.error("DomainModel属性赋值异常, PO = {}", JSONObject.toJSON(po), e);
            return null;
        }

        return domainModel;
    }

    @Override
    public P convert(D model) {
        Class<P> poClass = getPoClass();
        if (model == null || poClass == null) {
            return null;
        }

         P po = null;
        try {
            po = poClass.newInstance();
        } catch (Exception e) {
            LogConstant.frameworkLogger.error("PoClass实例化失败, model = {}", JSONObject.toJSON(model), e);
            return null;
        }

        if (po == null) {
            LogConstant.frameworkLogger.warn("PoClass实例化失败, model = {}", JSONObject.toJSON(model));
            return null;
        }

        Field[] poFields = FieldUtils.getAllFields(po.getClass());

        try {
            for (Field field : poFields) {
                writeProperty(field, model, po);
            }
        } catch (Exception e) {
            LogConstant.frameworkLogger.error("PoClass属性赋值异常, model = {}", JSONObject.toJSON(model), e);
            return null;
        }

        return po;
    }

    Object readProperty(Field field, Object source) throws IllegalAccessException {
        Assert.notNull(source, "field must not be null");
        Assert.notNull(source, "Source must not be null");

        field.setAccessible(true);
        return field.get(source);
    }

    void writeProperty(Field field, Object source, Object target) throws IllegalAccessException, InvocationTargetException, IntrospectionException {
        Assert.notNull(field, "field must not be null");
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");

        Object fieldValue = readProperty(field, source);

        if (fieldValue == null || ignoreField.contains(field.getName())) {
            return;
        }

        DomainField annotation = field.getAnnotation(DomainField.class);

        if (annotation != null) {
            String domainCode = annotation.code();
            String domainField = annotation.fieldName();

            IDomainService domainService = DomainRegistryFactory.getDomainService(domainCode);

            if (domainService == null) {
                LogConstant.frameworkLogger.error("使用DomainField注解的字段，需搭配实现{@link com.xy.ddd.core.model.DomainModel}");
                return;
            }

            boolean isSingle = field.getType().getSimpleName().endsWith("List");

            Object value = FieldUtils.readDeclaredField(source, domainField);

            if (isSingle) {
                D domainModel = (D) domainService.findOneByField(domainField, value);
                FieldUtils.writeDeclaredField(target, field.getName(), domainModel);
            } else {
                List<D> list = domainService.findListByField(domainField, value);
                FieldUtils.writeDeclaredField(target, field.getName(), list);
            }

        } else {
            FieldUtils.writeDeclaredField(target, field.getName(), fieldValue);
        }
    }

}
